package com.example.threadstack;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.example.threadstack.Utils.getCurrentTime;
import static com.example.threadstack.Utils.sleepForMs;

@RestController
@RequestMapping("deadlock")
public class DeadLockTestController {
    private ExecutorService deadlockExecutor = Executors.newFixedThreadPool(20, new BasicThreadFactory
            .Builder()
            .namingPattern("DeadLock-Thread-%d")
            .build()
    );

    @RequestMapping("object")
    public DeferredResult<String> object(){
        DeferredResult<String> result = new DeferredResult<>(10 * 1000L, "TimeOut");
        CompletableFuture<String> r1 = CompletableFuture.supplyAsync(this::objectMethod1, this.deadlockExecutor);
        CompletableFuture<String> r2 = CompletableFuture.supplyAsync(this::objectMethod2, this.deadlockExecutor);
        CompletableFuture.allOf(r1, r2).thenRun( ()-> result.setResult("SUCCESS"));
        return result;
    }

    @RequestMapping("lock")
    public DeferredResult<String> lock(){
        DeferredResult<String> result = new DeferredResult<>(10 * 1000L, "TimeOut");
        CompletableFuture<String> r1 = CompletableFuture.supplyAsync(this::lockMethod1, this.deadlockExecutor);
        CompletableFuture<String> r2 = CompletableFuture.supplyAsync(this::lockMethod2, this.deadlockExecutor);
        CompletableFuture.allOf(r1, r2).thenRun( ()-> result.setResult("SUCCESS"));
        return result;
    }


    private final Object objectA = new Object();
    private final Object objectB = new Object();

    private String objectMethod1(){
        synchronized (this.objectA){
            sleepForMs(10);
            synchronized (this.objectB){
                return getCurrentTime();
            }
        }
    }

    private String objectMethod2(){
        synchronized (this.objectB){
            sleepForMs(10);
            synchronized (this.objectA){
                return getCurrentTime();
            }
        }
    }


    private final Lock lockA = new ReentrantLock();
    private final Lock lockB = new ReentrantLock();

    private String lockMethod1(){
        try {
            this.lockA.lock();
            sleepForMs(10);
            try {
                this.lockB.lock();
                return getCurrentTime();
            }finally {
                this.lockB.unlock();;
            }
        }finally {
            this.lockA.unlock();
        }
    }

    private String lockMethod2(){
        try {
            this.lockB.lock();
            sleepForMs(10);
            try {
                this.lockA.lock();
                return getCurrentTime();
            }finally {
                this.lockA.unlock();;
            }
        }finally {
            this.lockB.unlock();
        }
    }




}
